﻿namespace CrossConnect.Common
{
    using System;
    using System.Collections.Generic;

    using Windows.Foundation;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Markup;

    /// <summary>
    ///     Wrapper for <see cref="RichTextBlock" /> that creates as many additional overflow
    ///     columns as needed to fit the available content.
    /// </summary>
    /// <example>
    ///     The following creates a collection of 400-pixel wide columns spaced 50 pixels apart
    ///     to contain arbitrary data-bound content:
    ///     <code>
    /// <RichTextColumns>
    ///     <RichTextColumns.ColumnTemplate>
    ///         <DataTemplate>
    ///             <RichTextBlockOverflow Width="400" Margin="50,0,0,0" />
    ///         </DataTemplate>
    ///     </RichTextColumns.ColumnTemplate>
    ///     <RichTextBlock Width="400">
    ///         <Paragraph>
    ///             <Run Text="{Binding Content}" />
    ///         </Paragraph>
    ///     </RichTextBlock>
    /// </RichTextColumns>
    /// </code>
    /// </example>
    /// <remarks>
    ///     Typically used in a horizontally scrolling region where an unbounded amount of
    ///     space allows for all needed columns to be created.  When used in a vertically scrolling
    ///     space there will never be any additional columns.
    /// </remarks>
    [ContentProperty(Name = "RichTextContent")]
    public sealed class RichTextColumns : Panel
    {
        #region Static Fields

        /// <summary>
        ///     Identifies the <see cref="ColumnTemplate" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty ColumnTemplateProperty = DependencyProperty.Register(
            "ColumnTemplate",
            typeof(DataTemplate),
            typeof(RichTextColumns),
            new PropertyMetadata(null, ResetOverflowLayout));

        /// <summary>
        ///     Identifies the <see cref="RichTextContent" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty RichTextContentProperty =
            DependencyProperty.Register(
                "RichTextContent",
                typeof(RichTextBlock),
                typeof(RichTextColumns),
                new PropertyMetadata(null, ResetOverflowLayout));

        #endregion

        #region Fields

        /// <summary>
        ///     Lists overflow columns already created.  Must maintain a 1:1 relationship with
        ///     instances in the <see cref="Panel.Children" /> collection following the initial
        ///     RichTextBlock child.
        /// </summary>
        private List<RichTextBlockOverflow> _overflowColumns;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="RichTextColumns" /> class.
        /// </summary>
        public RichTextColumns()
        {
            this.HorizontalAlignment = HorizontalAlignment.Left;
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///     Gets or sets the template used to create additional
        ///     <see cref="RichTextBlockOverflow" /> instances.
        /// </summary>
        public DataTemplate ColumnTemplate
        {
            get
            {
                return (DataTemplate)this.GetValue(ColumnTemplateProperty);
            }
            set
            {
                this.SetValue(ColumnTemplateProperty, value);
            }
        }

        /// <summary>
        ///     Gets or sets the initial rich text content to be used as the first column.
        /// </summary>
        public RichTextBlock RichTextContent
        {
            get
            {
                return (RichTextBlock)this.GetValue(RichTextContentProperty);
            }
            set
            {
                this.SetValue(RichTextContentProperty, value);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Arranges the original content and all extra columns.
        /// </summary>
        /// <param name="finalSize">
        ///     Defines the size of the area the children must be arranged
        ///     within.
        /// </param>
        /// <returns>The size of the area the children actually required.</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            double maxWidth = 0;
            double maxHeight = 0;
            foreach (UIElement child in this.Children)
            {
                child.Arrange(new Rect(maxWidth, 0, child.DesiredSize.Width, finalSize.Height));
                maxWidth += child.DesiredSize.Width;
                maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
            }
            return new Size(maxWidth, maxHeight);
        }

        /// <summary>
        ///     Determines whether additional overflow columns are needed and if existing columns can
        ///     be removed.
        /// </summary>
        /// <param name="availableSize">
        ///     The size of the space available, used to constrain the
        ///     number of additional columns that can be created.
        /// </param>
        /// <returns>The resulting size of the original content plus any extra columns.</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            if (this.RichTextContent == null)
            {
                return new Size(0, 0);
            }

            // Make sure the RichTextBlock is a child, using the lack of
            // a list of additional columns as a sign that this hasn't been
            // done yet
            if (this._overflowColumns == null)
            {
                this.Children.Add(this.RichTextContent);
                this._overflowColumns = new List<RichTextBlockOverflow>();
            }

            // Start by measuring the original RichTextBlock content
            this.RichTextContent.Measure(availableSize);
            double maxWidth = this.RichTextContent.DesiredSize.Width;
            double maxHeight = this.RichTextContent.DesiredSize.Height;
            bool hasOverflow = this.RichTextContent.HasOverflowContent;

            // Make sure there are enough overflow columns
            int overflowIndex = 0;
            while (hasOverflow && maxWidth < availableSize.Width && this.ColumnTemplate != null)
            {
                // Use existing overflow columns until we run out, then create
                // more from the supplied template
                RichTextBlockOverflow overflow;
                if (this._overflowColumns.Count > overflowIndex)
                {
                    overflow = this._overflowColumns[overflowIndex];
                }
                else
                {
                    overflow = (RichTextBlockOverflow)this.ColumnTemplate.LoadContent();
                    this._overflowColumns.Add(overflow);
                    this.Children.Add(overflow);
                    if (overflowIndex == 0)
                    {
                        this.RichTextContent.OverflowContentTarget = overflow;
                    }
                    else
                    {
                        this._overflowColumns[overflowIndex - 1].OverflowContentTarget = overflow;
                    }
                }

                // Measure the new column and prepare to repeat as necessary
                overflow.Measure(new Size(availableSize.Width - maxWidth, availableSize.Height));
                maxWidth += overflow.DesiredSize.Width;
                maxHeight = Math.Max(maxHeight, overflow.DesiredSize.Height);
                hasOverflow = overflow.HasOverflowContent;
                overflowIndex++;
            }

            // Disconnect extra columns from the overflow chain, remove them from our private list
            // of columns, and remove them as children
            if (this._overflowColumns.Count > overflowIndex)
            {
                if (overflowIndex == 0)
                {
                    this.RichTextContent.OverflowContentTarget = null;
                }
                else
                {
                    this._overflowColumns[overflowIndex - 1].OverflowContentTarget = null;
                }
                while (this._overflowColumns.Count > overflowIndex)
                {
                    this._overflowColumns.RemoveAt(overflowIndex);
                    this.Children.RemoveAt(overflowIndex + 1);
                }
            }

            // Report final determined size
            return new Size(maxWidth, maxHeight);
        }

        /// <summary>
        ///     Invoked when the content or overflow template is changed to recreate the column layout.
        /// </summary>
        /// <param name="d">
        ///     Instance of <see cref="RichTextColumns" /> where the change
        ///     occurred.
        /// </param>
        /// <param name="e">Event data describing the specific change.</param>
        private static void ResetOverflowLayout(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // When dramatic changes occur, rebuild the column layout from scratch
            var target = d as RichTextColumns;
            if (target != null)
            {
                target._overflowColumns = null;
                target.Children.Clear();
                target.InvalidateMeasure();
            }
        }

        #endregion
    }
}